package com.googlecode.gamedroid.event.source;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.googlecode.gamedroid.event.Event;
import com.googlecode.gamedroid.event.EventKey;
import com.googlecode.gamedroid.event.EventReceiver;
import com.googlecode.gamedroid.event.EventType;

public class Stick extends View implements EventSource
{
	private static final double PADDING = 0.1;
	
	private int mActivePointer = -1;
	private int mCenterX;
	private int mCenterY;
	private double[] mCoordinate = { 0, 0 };
	private Event[] mEvents =
	{
		new Event(EventType.ABS, EventKey.ABS_X),
		new Event(EventType.ABS, EventKey.ABS_Y)
	};
	private int mHeight = 0;
	private double mLastX = 0;
	private double mLastY = 0;
	private Paint mPaintBg;
	private Paint mPaintFg;
	private Path mPathClip;
	private int mRadius;
	private EventReceiver mReceiver = null;
	private int mWidth = 0;
	
	public Stick(Context context)
	{
		super(context);
		onLoad();
	}
	
	public Stick(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		onLoad();
	}
	
	public Stick(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		onLoad();
	}
	
	private void onLoad()
	{
		mPaintBg = new Paint();
		mPaintBg.setColor(0xFFFFFFFF);
		mPaintBg.setStyle(Paint.Style.STROKE);
		mPaintBg.setStrokeWidth(2);
		
		mPaintFg = new Paint();
		mPaintFg.setColor(0xFFFF0000);
		mPaintFg.setStyle(Paint.Style.STROKE);
		mPaintFg.setStrokeWidth(2);
		
		mPathClip = new Path();
	}
	
	private void sendEvent()
	{
		if(mCoordinate[0] == mLastX && mCoordinate[1] == mLastY)
			return;
		
		mLastX = mCoordinate[0];
		mLastY = mCoordinate[1];
		
		invalidate();
		
		if(mReceiver == null)
			return;
		
		mEvents[0].setValue(mCoordinate[0]);
		mEvents[1].setValue(mCoordinate[1]);
		
		mReceiver.emit(mEvents, true);
	}
	
	private void transformCoordinate(float x, float y)
	{
		mCoordinate[0] = Math.min(1, Math.max(-1, (x - mCenterX) / mRadius));
		mCoordinate[1] = Math.min(1, Math.max(-1, (y - mCenterY) / mRadius));
	}
	
	@Override
	protected void onDraw(Canvas canvas)
	{
		canvas.drawARGB(0, 0, 0, 0);
		
		canvas.drawLine(mCenterX, mCenterY - mRadius, mCenterX, mCenterY + mRadius, mPaintBg);
		canvas.drawLine(mCenterX - mRadius, mCenterY, mCenterX + mRadius, mCenterY, mPaintBg);
		
		mPathClip.reset();
		mPathClip.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CCW);
		canvas.clipPath(mPathClip, Region.Op.REPLACE);
		
		float pointX = (float) (mCenterX + mCoordinate[0] * mRadius);
		float pointY = (float) (mCenterY + mCoordinate[1] * mRadius);
		
		canvas.drawCircle(
			pointX,
			pointY,
			mRadius / 2, mPaintFg);
		
		canvas.drawLine(pointX - mRadius / 2, pointY, pointX + mRadius / 2, pointY, mPaintFg);
		canvas.drawLine(pointX, pointY - mRadius / 2, pointX, pointY + mRadius / 2, mPaintFg);
		
		canvas.drawCircle(mCenterX, mCenterY, mRadius - 1, mPaintBg);
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int w = getMeasuredWidth();
		int h = getMeasuredHeight();
		
		if(w < h)
			h = w;
		else
			w = h;
		
		int measuredW = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.getMode(getMeasuredWidthAndState()));
		int measuredH = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.getMode(getMeasuredHeightAndState()));
		
		setMeasuredDimension(measuredW, measuredH);
	}
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh)
	{
		super.onSizeChanged(w, h, oldw, oldh);
		
		mWidth = w;
		mHeight = h;
		
		mCenterX = mWidth / 2;
		mCenterY = mHeight / 2;
		
		mRadius = (int) (Math.min(mWidth, mHeight) / 2 * (1 - PADDING));
	}
	
	@Override
	public Event[] getEventDefinitions()
	{
		return mEvents;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		int index;
		switch(event.getAction() & MotionEvent.ACTION_MASK)
		{
			case MotionEvent.ACTION_DOWN:
			case MotionEvent.ACTION_POINTER_DOWN:
				if(mActivePointer != -1)
					break;
				index = event.getActionIndex();
				mActivePointer = event.getPointerId(index);
				
				transformCoordinate(event.getX(index), event.getY(index));
				sendEvent();
				if(mReceiver != null)
					mReceiver.feedback();
				return true;
				
			case MotionEvent.ACTION_MOVE:
				if(mActivePointer == -1)
					break;
				index = event.findPointerIndex(mActivePointer);
				
				if(index == -1)
				{
					mActivePointer = -1;
					mCoordinate[0] = mCoordinate[1] = 0;
					sendEvent();
					if(mReceiver != null)
						mReceiver.feedback();
					break;
				}
				
				transformCoordinate(event.getX(index), event.getY(index));
				sendEvent();
				return true;
				
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_POINTER_UP:
				if(mActivePointer == -1)
					break;
				index = event.findPointerIndex(mActivePointer);
				if(index != -1 && index != event.getActionIndex())
					break;
				
				mActivePointer = -1;
				mCoordinate[0] = mCoordinate[1] = 0;
				sendEvent();
				if(mReceiver != null)
					mReceiver.feedback();
				return true;
		}
		return super.onTouchEvent(event);
	}
	
	@Override
	public void reset()
	{
		mActivePointer = -1;
		mCoordinate[0] = 0;
		mCoordinate[1] = 0;
		sendEvent();
	}
	
	@Override
	public void setEvents(short[] events)
	{
		if(events.length != 2)
			throw new IllegalArgumentException();
		
		mEvents[0].setKey(events[0]);
		mEvents[1].setKey(events[1]);
	}
	
	@Override
	public void setReceiver(EventReceiver receiver)
	{
		mReceiver = receiver;
	}
}
